Blog

  • 2024
  • 2023
  • 2022
  • 2021
  • 2020
  • 2019
  • 2018
  • 2017
  • 2016
  • 2015
  • 2014
  • 2013
  • 2012

Last week I watched a video showing a talk by Kevin Bourrillion introducing Guava, a set of open source core libraries used internally by Google.

Kevin Bourrillion mentioned the library's Preconditions class that is closely related to Apache's Validate or Java 7's Objects. Basically all three implementations provide means to check preconditions. This is especially useful to check passed in arguments to fail-fast. At smartics we provided our own version of argument checking some time ago. It is called Arguments and is especially helpful to check for non-null or non-blank values.

The Idea for a Change

There is an interesting feature in Google's Preconditions class that we do not cover in our library: It returns the checked value. This makes it possible to reformulate the following

public MyConstructor(final String string, final Integer integer) {
  Arguments.checkNotBlank("string", string);
  Arguments.checkNotNull("integer", integer);
 
  this.string = string;
  this.integer = integer;
}

to

public MyConstructor(final String string, final Integer integer) {
  this.string = Arguments.checkNotBlank("string", string);
  this.integer = Arguments.checkNotNull("integer", integer);
}

This makes the code half as long.

Returning the value also allows to use the check in constructor chaining situations. It may get ugly, but in some situations it may make the failure condition more clear:

public MyConstructor(final String myDomainIdentifier) {
  this(new Whatever(
     checkNotBlank("myDomainIdentifier", myDomainIdentifier)));
}

This might get a better message in the exception if Whatever simply refers to a string, since the constructor above still ‘knows’ the name of the parameter to include it in the error message.

Now go for it!

Both scenarios I want to support in the next version of our library. I still want to stick to our version of the argument checking helper class, because I often have to check for argument values not being blank.

So all I have to do is return the passed in argument? “Wrong!” you shout and you are right.

If I change

public static void checkNotBlank(final String name,
  final String value, final String message) 
  throws BlankArgumentException {
    if (StringUtils.isBlank(value)) {
      throw new BlankArgumentException(name, message);
    }
  }
}

to

public static String checkNotBlank(final String name,
  final String value, final String message)
  throws BlankArgumentException {
    if (StringUtils.isBlank(value)) {
      throw new BlankArgumentException(name, message);
    }
 
    return value;
  }
}

This change does not break the source code. But I introduce an incompatible change with the previous version that occurs at runtime. This is because changing the return value requires the compiler to create a new method and removing the old. So the change is not binary compatible.

The relevant part in the Java Language Specification:

Changing the result type of a method, replacing a result type with void, or replacing void with a result type has the combined effect of deleting the old method and adding a new method with the new result type or newly void result (see §13.4.12).

For purposes of binary compatibility, adding or removing a method or constructor m whose return type involves type variables (§4.4) or parameterized types (§4.5) is equivalent to the addition (respectively, removal) of the an otherwise equivalent method whose return type is the erasure (§4.6) of the return type of m.

Java Language Specification, 13.4.15 Method Result Type

For more information about this topic, please refer to polygenelubricants‘s answer to the question Retrofitting void methods to return its argument to facilitate fluency: breaking change? on StackOverflow.

This problem is reported by the clirr-maven-plugin that can easily be integrated into any Maven build process.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>clirr-maven-plugin</artifactId>
  <version>2.3</version>
  <configuration>
    <minSeverity>info</minSeverity>
  </configuration>
</plugin>

The report for the incompatible change looks like this on the Clirr report on the project’s Maven Site:

The fix in our case is quite easy if you control all dependent projects: You simply have to recompile. No source code change is required. But in reality this is not feasible. Either you do not have all dependent binaries in your control or you just do not have the resources to recompile and test them all. So the solution to this problem is to be found elsewhere.

Do not break Things

For libraries we have to be very sensitive to changes that break the API. In larger projects there may be a couple of modules that depend on the same library and if there is a change that broke the API all modules have to grade up to work properly together again. Therefore for e.g. Apache makes it easy to use their version 3 of commons-lang alongside with their version 2.

First they provided a new artifact ID:

<dependency>
  <groupId>commons-lang</groupId>
  <artifactId>commons-lang</artifactId>
  <version>2.6</version>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.1</version>
</dependency>

This allows Maven to resolve a dependency set that includes both libraries. This way some modules can use the new version and other modules can update as soon as they want to.

To succeed in this, the package name has also be adjusted. Where in Version 2 the package for e.g. the Validate class mentions above was

org.apache.commons.lang.Validate

in Version 3 it is called:

org.apache.commons.lang3.Validate

So the library in version 3 can live along with any previous version, since Validate is effectively a new class.

Mark as deprecated

But in our case we do not have to go this far. We are not planning to release a new major version any time soon. Therefore we have to introduce a new class with the new behavior. It is called Arg. The old Arguments class is now marked deprecated.

/**
 * Utility class to check arguments.
 *
 * @deprecated Due to a breaking change a new class called {@link Arg} has been
 *             designed to be used from now on. It has the same methods as this
 *             one, but it returns the value passed in to check. This allows to
 *             do the check and an initialization (e.g. in a constructor) in one
 *             line. It also allows to check arguments if another constructor is
 *             called (such as in <code>this()</code> and <code>super()</code>).
 *             This class is planned to be removed with the release of version 
 *             2.0.
 */
@Deprecated
public final class Arguments {
  ...
}

We inform our API users

  1. where the new version can be found
  2. why we made the change
  3. when this class will go out of service

The deprecation mark is translated into a warning by the compiler. If a developer is in doubt whether to use Arguments or Arg she simply has not remember which is newer. This helps to move the code towards the new version supported by the compiler.

The non-breaking change is reported in the Clirr report like this:

 

Everything green again. No headaches for anyone in the future.

Using complex data types in Maven plugin configurations is quite easy. There is a good and brief documentation within the Maven site that shows how to use them in your plugin: Mapping Complex Objects in Guide to Configuring Plug-ins.

What is not mentioned in this document is how the properties are injected. It seems that setters are used, if they are present. If not, injection occurs with the fields via reflection. So the following two configuration approaches are successful.

Field Injection

This example shows field injection together with a property type that is defined within the plugin’s project.

Configuration

The key contains (for simplicity) only one element, an identifier of the environment.

<configuration>
  <key>
    <environment>test</environment>
  </key>
</configuration>

Mojo Implementation

The field is required to be named key.

public final class MyMojo extends AbstractMojo {
  /**
   * @parameter
   */
  private Key key;
  ...
}

Property Type Implementation

It is important that the name of the type matches that of the parameter in the Mojo (the first letter is capitalized as is standard for Java Beans).

public class Key {
  private String environment;
 
  public String getEnvironment() {
    return environment;
  }
}

There is no setter, so Maven injects the value in the private field environment. The name of the field and the name of the property within the configuration are the same.

Setter Injection

This setter injection example shows the configuration of a property (a factory), whose type is defined outside of the Maven plugin project. The implementation attribute allows to select an implementation that is used as a complex property.

Configuration

The configuration references an implementation of the PropertySinkFactory interface.

<configuration>
  <propertySinkFactory 
      implementation="de.smartics.properties.config.transfer.filesystem.Factory">
    <targetFolder>${basedir}/target/testme</targetFolder>
    <outputFormat>xml</outputFormat>
  </propertySinkFactory>
</configuration>

Mojo Implementation

There is no naming constraint.

public final class MyMojo extends AbstractMojo {
  /**
   * @parameter
   */
  private PropertySinkFactory<?> propertySinkFactory
  ...
}

Property Type Implementation

There are no constraints on the property type name. The properties of the type have to match the element names of the configuration (e.g. targetFolder).

 package de.smartics.properties.config.transfer.filesystem;
 
import java.io.File;
import java.io.IOException;
 
import de.smartics.properties.api.config.transfer.PropertySinkFactory;
import de.smartics.util.io.FileFunction;
 
public final class Factory implements
    PropertySinkFactory<FileSystemPropertySink>
{
  private File targetFolder;
  private PropertiesFormat outputFormat = PropertiesFormat.NATIVE;
 
  public Factory() {
  }
 
  public void setTargetFolder(final String targetFolder) {
    this.targetFolder = new File(targetFolder);
    try
    {
      FileFunction.provideDirectory(this.targetFolder);
    }
    catch (final IOException e)
    {
      final String message =
          String.format("Cannot create target folder '%s'.",
              this.targetFolder.getAbsolutePath());
      throw new IllegalArgumentException(message, e);
    }
  }
 
  public void setOutputFormat(final String outputFormat) {
    this.outputFormat = PropertiesFormat.fromString(outputFormat);
  }
 
  ...
}

The example shows how String parameter values are translated to their domain types within the setter methods.

Conclusion

The examples show more detailed than the original documentation how complex properties can be used in Maven plugins.